package org.example.service;

/**
 * @author by PengHongChao
 * @date 2025/6/23 15:44
 */

import com.cronutils.model.Cron;
import com.cronutils.model.CronType;
import com.cronutils.model.definition.CronDefinitionBuilder;
import com.cronutils.model.time.ExecutionTime;
import com.cronutils.parser.CronParser;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import lombok.extern.slf4j.Slf4j;
import org.example.mapper.SyncConfigMapper;
import org.example.model.SyncConfig;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * 基于时间轮算法的精确调度器
 * 支持毫秒级精度的定时任务调度
 */
@Slf4j
@Component
@Lazy(false) // 确保在应用启动时初始化
public class PrecisionScheduler implements InitializingBean {

    // 时间轮实例（单例）
    private final HashedWheelTimer timer = new HashedWheelTimer(
            100, TimeUnit.MILLISECONDS, 512); // 100ms刻度，512个槽

    // 任务映射表：configId -> Timeout
    private final Map<Long, Timeout> taskTimeouts = new ConcurrentHashMap<>();

    private final SyncConfigMapper configMapper;
    private final SyncService syncService;

    public PrecisionScheduler(SyncConfigMapper configMapper,
                              SyncService syncService, ThreadPoolTaskExecutor taskExecutor) {
        this.configMapper = configMapper;
        this.syncService = syncService;
        this.taskExecutor = taskExecutor;
    }

    /**
     * 注入现有的线程池
     */
    private final ThreadPoolTaskExecutor taskExecutor;
    /**
     * 新增Cron解析器
     */
    private final CronParser cronParser = new CronParser(
            CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ)
    );

    @Override
    public void afterPropertiesSet() {
        // 启动时间轮
        timer.start();
        log.info("时间轮调度器已启动");

        // 初始化加载所有启用的任务
        reloadAllTasks();
    }

    @PreDestroy
    public void destroy() {
        // 应用关闭时停止时间轮
        timer.stop();
        log.info("时间轮调度器已停止");
    }

    /**
     * 加载所有启用的任务
     */
    public void reloadAllTasks() {
        // 取消所有现有任务
        taskTimeouts.values().forEach(Timeout::cancel);
        taskTimeouts.clear();

        // 加载并调度所有启用的任务
        List<SyncConfig> configs = configMapper.selectAllEnabled();
        configs.forEach(this::scheduleTask);
        log.info("已加载 {} 个同步任务", configs.size());
    }

    /**
     * 调度单个任务
     *
     * @param config 同步配置
     */
    public void scheduleTask(SyncConfig config) {
        // 取消现有任务（如果存在）
        cancelTask(config.getId());

        // 计算首次执行延迟
        long initialDelay = calculateInitialDelay(config);

        // 直接调用执行方法，而不是创建额外的任务包装
        TimerTask task = timeout -> executeSyncTask(config);

        Timeout newTimeout = timer.newTimeout(task, initialDelay, TimeUnit.MILLISECONDS);
        taskTimeouts.put(config.getId(), newTimeout);

        log.info("已调度表 {}，首次执行将在 {} 毫秒后", config.getSourceTable(), initialDelay);
    }

    /**
     * 重新调度下一次执行
     */
    private void reschedule(SyncConfig config) {
        // 检查任务是否已被取消
        if (!taskTimeouts.containsKey(config.getId())) {
            log.info("任务 {} 已被取消，不再重新调度", config.getId());
            return;
        }
        long nextDelay;
        // Cron模式
        if ("CRON".equals(config.getScheduleType()) && config.getCronExpression() != null) {
            nextDelay = calculateCronDelay(config.getCronExpression());
        }
        // 固定间隔模式
        else {
            nextDelay = TimeUnit.MINUTES.toMillis(config.getSyncInterval());
        }

        // 创建新的定时任务
        TimerTask task = timeout -> executeSyncTask(config);

        Timeout newTimeout = timer.newTimeout(task, nextDelay, TimeUnit.MILLISECONDS);
        taskTimeouts.put(config.getId(), newTimeout);

        log.debug("任务 {} 将在 {} 毫秒后执行", config.getId(), nextDelay);
    }

    // 执行同步任务的方法
    private void executeSyncTask(SyncConfig config) {
        taskExecutor.submit(() -> {
            try {
                syncService.syncTable(config);
            } finally {
                // 确保重新调度在任务线程执行
                reschedule(config);
            }
        });
    }

    /**
     * 取消任务
     *
     * @param configId 配置ID
     */
    public void cancelTask(Long configId) {
        Timeout timeout = taskTimeouts.remove(configId);
        if (timeout != null) {
            timeout.cancel();
            log.info("已取消任务: {}", configId);
        }
    }

    /**
     * 计算首次执行延迟
     */
    private long calculateInitialDelay(SyncConfig config) {
        // Cron模式计算
        if ("CRON".equals(config.getScheduleType()) && config.getCronExpression() != null) {
            return calculateCronDelay(config.getCronExpression());
        }
        //固定间隔模式 立即执行
        if (config.getLastSyncTime() == null) {
            return 0;
        }

        // 计算下次应该执行的时间
        LocalDateTime nextExecution = config.getLastSyncTime()
                .plusMinutes(config.getSyncInterval());

        // 计算与当前时间的差值（毫秒）
        long delay = Duration.between(LocalDateTime.now(), nextExecution).toMillis();

        // 如果已经过了执行时间，立即执行
        return Math.max(0, delay);
    }

    /**
     * Cron延迟计算
     *
     * @param cronExpression
     * @return
     */
    private long calculateCronDelay(String cronExpression) {
        try {
            Cron cron = cronParser.parse(cronExpression);
            ExecutionTime executionTime = ExecutionTime.forCron(cron);

            Optional<Duration> duration = executionTime.timeToNextExecution(
                    ZonedDateTime.now()
            );

            return duration.map(Duration::toMillis).orElse(0L);
        } catch (Exception e) {
            log.error("Cron表达式解析失败: {}", cronExpression, e);
            return 0;
        }
    }
}
